import asyncio
import threading


class ObjectPool:
    def __init__(self, object_factory: any, build_objects: int, max_objects=10):
        self.object_factory = object_factory
        self.max_objects = max_objects
        self.available_objects = []
        self._used_objects = []
        self._max_size = min(build_objects, max_objects)
        self.__lock = threading.Lock()

    async def load(self):
        with self.__lock:
            loop = asyncio.get_running_loop()
            if len(self._used_objects) == 0:
                _objs = [loop.run_in_executor(None, self.object_factory) for i in range(self._max_size)]
                self._used_objects.extend(await asyncio.gather(*_objs))

    async def acquire(self) -> any:
        if len(self._used_objects) == 0:
            await self.load()
        obj = None
        if self.available_objects:
            obj = self.available_objects.pop()
        elif len(self._used_objects) < self.max_objects:
            obj = self.object_factory()
        else:
            while True:
                await asyncio.sleep(0)
                if self.available_objects:
                    obj = self.available_objects.pop()
                    break
        self._used_objects.append(obj)
        return obj

    def release(self, obj):
        if obj in self._used_objects:
            self._used_objects.remove(obj)
            self.available_objects.append(obj)

    def size(self):
        return len(self.available_objects)
